<?php
namespace classlib\core;
use classlib\orm;
use classlib\database\query;
use classlib\cache\Icache;
abstract class model {
	/**
	 * php内存cache数据对象缓存
	 * @staticvar
	 * @var array
	 */
	static $cache  = array();
	
	/**
	 * 加载数据库对象模型
	 * @staticvar
	 * @var array
	 */
	static $models = array();
	
	/**
	 * 缓存的设置对象
	 * @var Icache
	 */
	static $cacher;
		
	/**
	 * 应用的数据版本号
	 * @var array
	 */
	private $cache_ver    = array();
	
	/**
	 * 设置缓存对象实例信息
	 * @param Icache $icache
	 * @return void
	 */
	public static function set_cacher($icache) {
		self::$cacher = $icache;
	}
	
	/**
	 * 获取生成的完成的缓存key值
	 * @param string $skey
	 */
	public function get_skey($skey) {
		return $this->table.'@'.$skey;
	}
	
	/**
	 * 获取Orm执行实例
	 * @return query $query;
	 */
	public function get_orm($db='writer') {
		$query = orm::inst($this->$db)->clear()->table($this->table);
		return $query;
	}
	
	/**
	 * 设置Orm单表操作的版本资料
	 * @param string $skey
	 * @return void
	 */
	public function set_ver($skey='dbVER') {
		$skey  = $this->get_skey($skey);
		$ver   = (int)self::$cacher->get($skey);
		$this->cache_ver[$skey] = (!$ver)? time():($ver + 1);
		self::$cacher->set($skey, $this->cache_ver[$skey]);
	}
	
	/**
	 * 获取当前表的版本资料
	 * @param string $szkey
	 * @return int
	 */
	public function get_ver($skey='dbVER') {
		$skey  = $this->get_skey($skey);
		if (!isset($this->cache_ver[$skey])) {
			$this->cache_ver[$skey] = (int)self::$cacher->get($skey);
			if (!$this->cache_ver[$skey]) {
				$this->cache_ver[$skey] = time();
				self::$cacher->set($skey, $this->cache_ver[$skey]);
			}
		}
		return $this->cache_ver[$skey];
	}
	
	/**
	 * 获取默认的模型
	 * @return array $field
	 */
	public function get_fields() {
		static $sfield = array();
		if (!isset($sfield[$this->orgtable])) {
			list($segment, $class) = explode('_', $this->orgtable, 2);
			$file  = COREDIR.'/src/model/'.$this->dbinc.'/'.$segment.'/fields/'.$class.'.cls.php';
			$sfield[$this->orgtable] = include $file;
		}
		return $sfield[$this->orgtable];
	}
	
	/**
	 * 转码每一个项目选项
	 * @param string $key
	 * @param mixed  $val
	 * @param array  $fields
	 * @return mixed
	 */
	public static function convert_items(&$val, $key, &$fields) {
		if (isset($fields[$key])) {
			$otype = gettype($fields[$key]);
			switch ($otype) {
				case 'double':
				case 'float':
					$val = floatval($val);
					break;
				case 'integer':
					//针对大整数的处理转义 使用浮点数存储
					$val = (PHP_INT_SIZE == 4)? floatval($val):intval($val);
					break;
				case 'string':
					$val = htmlspecialchars_decode($val);
					break;
			}
		}
		return $val;
	}
	
	/**
	 * 数值类型转换然后再做输出  目前只支持二维度数组
	 * @return void
	 */
	protected function convert(&$data, $level=1) {
		$fields = $this->get_fields();
		if (is_array($data)) {
			if ($level == 1) {
				array_walk($data, '\classlib\core\model::convert_items', $fields);
			} else if ($level == 2) {
				foreach ($data as $idx=>&$item) {
					array_walk($item, '\classlib\core\model::convert_items', $fields);
				}
			}
		}
	}
	
	/**
	 * 执行添加条件
	 * @param query $query
	 * @param array $where
	 * @return
	 */
	protected function add_where($query, $where) {
		foreach ($where as $wfield=>$wvalue) {
			if (is_array($wvalue)) {
				$len = count($wvalue);
				if ($len == 2) {
					$query->where($wvalue[0], $wvalue[1]);
				} else if ($len == 3) {
					$query->where($wvalue[0], $wvalue[1], $wvalue[2]);
				} else if ($len == 4) {
					$query->where($wvalue[0], $wvalue[1], $wvalue[2], $wvalue[3]);
				} else if ($len == 1) {
					$query->where($wvalue[0]);
				}
			} else {
				$query->where($wfield, $wvalue);
			}
		}
	}
	
	/**
	 * 添加排序条件
	 * @param query $query
	 * @param mix $sorts
	 * @param string $dir
	 */
	protected function add_sort($query, $sorts, $dir='') {
		if (is_array($sorts)) {
			foreach ($sorts as $sort=>$dir) {
				if ($sort && $dir) {
					$query->orderby($sort, $dir);
				}
			}
		} else if($sorts && $dir) {
			$query->orderby($sorts, $dir);
		}
	}
	
	/**
	 * 数据新增一个实例
	 * @param array $field
	 * @return int $insertid or false
	 */
	public function new_one($field, $ofield=false) {
		$sfield = $this->get_fields();
		foreach ($sfield as $key=>&$value) {
			if (isset($field[$key])) { //有设置值的话设置到默认对象中 并转码
				$value = (is_string($value) && is_string($field[$key]))? htmlspecialchars($field[$key]):$field[$key];
			}
			//设置转码更新的字段资料信息
			if (isset($ofield[$key]) && is_string($value) && is_string($ofield[$key])) {
				$ofield[$key] = htmlspecialchars($ofield[$key]);
			}
		}
		$orm = orm::inst($this->writer)->clear()->table($this->table);
		($ofield && is_array($ofield)) && $orm->duplicate($ofield);
		$orm->insert($sfield);
		($ofield && isset($ofield[$this->prikey]) && $ofield[$this->prikey])?
			$this->idclear($ofield[$this->prikey]):$this->set_ver();
		return $orm->get_parser()->last_insert_id();
	}
	
	/**
	 * 获取一个对象实例
	 * @param int $id
	 * @return array
	 */
	public function get_one($id) {
		if(!isset(self::$cache[$this->table][$id])) {
			$skey = $this->get_skey($id);
			$data = self::$cacher->get($skey);
			if (!$data) {
				$data = orm::inst($this->reader)->clear()
					->table($this->table)->where($this->prikey, $id)->get_row();
				$this->convert($data);
				$data && self::$cacher->set($skey, $data, 86400);
			}
			self::$cache[$this->table][$id] = $data;
		}
		return self::$cache[$this->table][$id];
	}
	
	/**
	 * 根据id删除缓存
	 * @param unknown $id
	 */
	public function idclear($id) {
		$skey  = $this->get_skey($id);
		self::$cacher->delete($skey);
		unset(self::$cache[$this->table][$id]);
		$this->set_ver();
	}
	
	/**
	 * 删除一个对象实例
	 * @param int $id
	 * @return int $affectrows
	 */
	public function delete($id) {
		orm::inst($this->writer)->clear()
			->table($this->table)->where($this->prikey, $id)->delete();
		$this->idclear($id);
		return orm::inst($this->writer)->get_parser()->get_affected_rows();
	}
	
	/**
	 * 数据保存对象实例
	 * @param id $id
	 * @param array $field
	 * @return int $affectrows
	 */
	public function save($id, $field) {
		$sfield = $this->get_fields();
		foreach ($sfield as $key=>&$value) {
			//设置转码更新的字段资料信息
			if (isset($field[$key]) && is_string($field[$key]) && is_string($value)) {
				$field[$key] = htmlspecialchars($field[$key]);
			}
		}
		orm::inst($this->writer)->clear()->table($this->table)
			->where($this->prikey, $id)->update($field);
		$this->idclear($id);
		return orm::inst($this->writer)->get_parser()->get_affected_rows();
	}
	
	/**
	 * 获取缓存key的Hash值
	 * @param unknown $args
	 * @return string
	 */
	public function hash($args) {
		return md5($this->table.serialize($args).$this->get_ver());
	}
	
	/**
	 * 只获取一列
	 * @param string $start
	 * @param string $limit
	 * @param array  $where
	 * @param string $field
	 * @param string $sort
	 * @param string $dir
	 * @return array $list=>数组列表
	 */
	public function get_column($start=0, $limit=-1, $where=array(), $field='*', $sort='', $dir='') {
		$skey = $this->get_skey('column-'.$this->hash(func_get_args()));
		$list = self::$cacher->get($skey);
		if (!is_array($list)) {
			$query = orm::inst($this->reader)->clear()->table($this->table);
			if (!empty($where)) {
				$this->add_where($query, $where);
			}
			$this->add_sort($query, $sort, $dir);
			$list  = $query->field($field)->get_column(null, $start, $limit);
			self::$cacher->set($skey, $list, 3600);
		}
		return $list;
	}
	
	/**
	 * 只获取一列
	 * @param string $start
	 * @param string $limit
	 * @param array  $where
	 * @param string $field
	 * @return array $mapper=>数组列表
	 */
	public function get_mapper($start=0, $limit=-1, $where=array(), $field='key,val') {
		$skey = $this->get_skey('mapper-'.$this->hash(func_get_args()));
		$maper= self::$cacher->get($skey);
		if (!is_array($maper)) {
			$query = orm::inst($this->reader)->clear()->table($this->table);
			if (!empty($where)) {
				$this->add_where($query, $where);
			}
			$list  = $query->field($field)->get_list(null, $start, $limit);
			$maper = array();
			if ($list) {
				foreach ($list as $item) {
					$maper[$item['key']] = $item['val'];	
				}
			}
			self::$cacher->set($skey, $maper, 3600);
		}
		return $maper;
	}
	
	/**
	 * 批量数据保存对象实例
	 * @param array $where
	 * @param array $fields
	 * @return int $affectrows
	 */
	public function multi_save($where, $fields) {
		$snum = 0;
		$ids  = $this->get_column(0, -1, $where, $this->prikey);
		if ($ids) {
			$query = orm::inst($this->writer)->clear()->table($this->table);
			if ($where) {
				$this->add_where($query, $where);
			}
			$query->update($fields);
			$snum = $query->get_parser()->get_affected_rows();
			foreach ($ids as $id) {//清理缓存
				self::$cacher->delete($this->get_skey($id));
				unset(self::$cache[$this->table][$id]);
			}
			$this->set_ver();
		}
		return $snum;
	}
	
	/**
	 * 验证记录是否存在
	 * @param array $where
	 * @return int  $id
	 */
	public function isexist($where) {
		$skey  = $this->get_skey('exist-'.$this->hash(func_get_args()));
		$id    = self::$cacher->get($skey);
		if (!is_numeric($id)) {
			$query   = orm::inst($this->writer)->clear()->table($this->table);
			foreach ($where as $field=>$value) {
				$query->where($field, $value);
			}
			$id = $query->field($this->prikey)->get_value();
			$id && self::$cacher->set($skey, $id, 3600);
		}
		return (int)$id;
	}
	
	/**
	 * 只获取一列
	 * @param array  $where
	 * @param string $field  
	 * @param string $sort  
	 * @param string $dir  
	 * @return array $item
	 */
	public function get_item($where=array(), $field='*', $sort='', $dir='') {
		$skey = $this->get_skey('item-'.$this->hash(func_get_args()));
		$item = self::$cacher->get($skey);
		if (!is_array($item)) {
			$query = orm::inst($this->reader)->clear()->table($this->table);
			if (!empty($where)) {
				$this->add_where($query, $where);
			}
			$this->add_sort($query, $sort, $dir);
			$item  = $query->field($field)->get_row();
			$this->convert($item);
			self::$cacher->set($skey, $item, 3600);
		}
		return $item;
	}
	
	/**
	 * 只获第一列数值
	 * @param array  $where
	 * @param string $field
	 * @param string $sort
	 * @param string $dir
	 * @return array $value
	 */
	public function get_value($field='', $where=array(), $sort='', $dir='') {
		$skey  = $this->get_skey('value-'.$this->hash(func_get_args()));
		$value = self::$cacher->get($skey);
		if (!$value) {
			$query = orm::inst($this->reader)->clear()->table($this->table);
			if (!empty($where)) {
				$this->add_where($query, $where);
			}
			$this->add_sort($query, $sort, $dir);
			$value = $query->field($field)->get_value();
			$fields= $this->get_fields();
			$value = self::convert_items($value, $field, $fields);
			self::$cacher->set($skey, $value, 3600);
		}
		return $value;
	}
	
	/**
	 * 只获满足条件的总数
	 * @param array  $where
	 * @param string $field  
	 * @return array $total=>'记录总数'
	 */
	public function get_total($where=array(), $field='count(1)') {
		$skey = $this->get_skey('total-'.$this->hash(func_get_args()));
		$total = self::$cacher->get($skey);
		if (!is_numeric($total)) {
			$query  = orm::inst($this->reader)->clear()->table($this->table);
			if (!empty($where)) {
				$this->add_where($query, $where);
			}
			$total  = (int)$query->field($field)->get_value();
			self::$cacher->set($skey, $total, 3600);
		}
		return $total;
	}
	
	/**
	 * 通过执行SQL直接查询结果
	 * @param string $sql
	 * @param bool   $isFirst
	 * @param number $start
	 * @param int    $limit
	 * @return mixed
	 */
	public function get_assql($sql, $isFirst, $start=0, $limit=-1) {
		$skey = $this->get_skey('assql-'.$this->hash(func_get_args()));
		$data = self::$cacher->get($skey);
		if (!$data) {
			$orm  = orm::inst($this->reader)->get_parser();
			$data = ($isFirst)? $orm->get_first($sql):$orm->get_all($sql, $start, $limit);
			self::$cacher->set($skey, $data, 3600);
		}
		return $data;
	}
	
	/**
	 * 获取orm的sql记录信息，cache中信息
	 * @param query $orm;
	 * @return array $list
	 */
	public function get_asorm($orm, $isFirst, $start=0, $limit=-1) {
		$args = func_get_args();
		$skey = $orm->hash_data($args);
		$skey = $this->get_skey('asorm-'.$skey);
		$data = self::$cacher->get($skey);
		if (!$data) {
			$data = ($isFirst)? $orm->get_row():$orm->get_list(NULL, $start, $limit);
			self::$cacher->set($skey, $data, 3600);
		}
		return $data;
	}
	
	
	/**
	 * 只获取列表
	 * @param string $start
	 * @param string $limit
	 * @param array  $where
	 * @param string $field  
	 * @param string $sort  
	 * @param string $dir  
	 * @return array $list=>数组列表
	 */
	public function get_list($start=0, $limit=-1, $where=array(), $field='*', $sort='', $dir='') {
		$skey = $this->get_skey('list-'.$this->hash(func_get_args()));
		$list = self::$cacher->get($skey);
		if (!is_array($list)) {
			$query  = orm::inst($this->reader)->clear()->table($this->table);
			if (!empty($where)) {
				$this->add_where($query, $where);
			}
			$this->add_sort($query, $sort, $dir);
			$list  = $query->field($field)->get_list(null, $start, $limit);
			$this->convert($list, 2);
			self::$cacher->set($skey, $list, 3600);
		}
		return $list;
	}
	
	/**
	 * 获取表名字
	 * @return string $table
	 */
	public function get_table() {
		return $this->table;
	}
	
	/**
	 * 设置Orm当前要操作的数据表信息
	 * @param string $table
	 * @return model
	 */
	public function set_table($table) {
		$this->table = $table;
		return $this;
	}
	
	/**
	 * 设置除法分表策略
	 * @param int $id
	 * @return model
	 */
	public function set_dev($id) {
		if ($this->divider > 1) {
			$this->table = sprintf('%s%d', $this->orgtable, intval($id / $this->divider));
			$this->create_table($this->orgtable, $this->table);
		}
		return $this;
	}
	
	/**
	 * 设置求余数分表策略
	 * @param int $id
	 * @return model
	 */
	public function set_mod($id) {
		if ($this->slot > 1) {
			$this->table = sprintf('%s%d', $this->orgtable, $id % $this->slot);
			$this->create_table($this->orgtable, $this->table);
		}
		return $this;
	}
	
	/**
	 * 按照年月日归档的表资料信息
	 * @param string $mft
	 * @return model
	 */
	public function set_ymd($mft='Ym') {
		$this->table = sprintf('%s%d', $this->orgtable, date($mft));
		$this->create_table($this->orgtable, $this->table);
		return $this;
	}
	
	/**
	 * 检测分表信息是否存在，不存在的话创建分表信息
	 * @param model  $query
	 * @param string $orgtable
	 * @param string $newtable
	 * @throws \InvalidArgumentException
	 */
	private function create_table($orgtable, $newtable) {
		$cache = \classlib\cache::inst('file');
		$skey  = 'systable@'.$newtable;
		//说明表已经创建过了
		if ($cache->get($skey)) {
			return;
		}
		$query = orm::inst($this->writer);
		$sql   = 'show TABLES like "'.$newtable.'"';
		$res   = $query->get_parser()->get_all($sql);
		if ($res) { //表检测已经是存在的表
			$cache->set($skey, 1);
			return;
		}
		$sql  = sprintf('show create table %s', $orgtable);
		$list = $query->get_list($sql, 0, -1, orm::FETCH_NUM);
		if (!$list) {
			throw new \InvalidArgumentException('Table auto create failed.');
		}
		$sql  = str_replace($orgtable, $newtable, $list[0][1]);
		$sql  = str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS ', $sql);
		if (!$query->get_parser()->execute($sql)) {
			throw new \InvalidArgumentException('Table auto create failed.');
		}
		$cache->set($skey, 1);
	}
	
	/**
	 * 获取分表信息，分多少张表
	 * @return int
	 */
	public function get_slot() {
		return $this->slot;
	}
	
	/**
	 * 从DB中获取已经生成的分表
	 * @return array
	 */
	public function db_table() {
		$sql    = 'show tables like "'.$this->orgtable.'%"';
		$tables = $this->get_orm()->get_column($sql, 0, -1);
		return $tables;
	}
	
	/**
	 * 重置成员原表资料信息
	 */
	public function reset() {
		$this->table = $this->orgtable;
		return $this;
	}
	
	/**
	 * 执行调用一个存储过程
	 * @param string $sql
	 * @return mix false|mix
	 */
	public function call($sql) {
		$db   = orm::inst($this->writer)->get_parser();
		$res  = $db->execute($sql);
		if (!$res) {
			return false;
		}
		$args = array_slice(func_get_args(), 1);
		if (empty($args)) {//没有参数返回的直接获取记录的影响数目
			$res = $db->get_affected_rows();
		} else {
			$sql = 'select '.implode(',', $args);
			$res = $db->get_first($sql);
		}
		$this->set_ver();
		return $res;
	}
	
	/**
	 * 加载数据模型而设置的类
	 * @param string $name
	 * @return model
	 */
	public static function load($name) {
		if (!isset(self::$models[$name])) {
			$nscls = sprintf('src\model\%s', str_replace('.', '\\', $name));
			self::$models[$name] = new $nscls;
		}
		return self::$models[$name];
	}

	/**
	 * 获取表的记录信息
	 * @param string $table
	 * @return int $autoid
	 */
	public static function max_id($table) {
		$orm  = self::load('main.sys.maxid')->get_orm();
		$maxid= $orm->get_value('call getMaxId("'.$table.'");');
		return $maxid;
	}
}